/**
 * 
 */
package cn.edu.cqu.bysj.wm.ago.anlys;

import java.util.HashMap;
import java.util.Map;

import org.neo4j.graphdb.Node;
import org.ujmp.core.Matrix;

import cn.edu.cqu.bysj.wm.ago.lp.LP;
import cn.edu.cqu.bysj.wm.ago.lp.LPCommunity;

/**
 * @author Meng
 * 
 */
public class ModularityLP {

	private static Matrix M;
	private static long n, e;

	// map of node id and its degree
	private static Map<Long, Integer> degree;

	// map of node id and community id it belongs to
	public static Map<Long, Long> nodeCommunitiy;

	// the Q value
	public static double Q;

	/**
	 * calc modularities of this solution
	 * 
	 * @param lp
	 */
	public static void calculate(LP lp) {
		degree = new HashMap<Long, Integer>();
		nodeCommunitiy = new HashMap<Long, Long>();

		M = lp.matrix;
		n = lp.n;
		e = lp.e;

		// degree can use the adj in lp
		for (Node node : lp.adjRel.keySet()) {
			degree.put(node.getId(), lp.adjRel.get(node).size());
		}

		calcNodeCommunity();
		Q = calcQ();
		Similarity.Qs.put(Similarity.SID, Q);

	}

	/**
	 * put the node and the community it belongs to map
	 */
	private static void calcNodeCommunity() {

		for (long cid : LPCommunity.communityLabel.keySet()) {
			int lab = LPCommunity.communityLabel.get(cid);
			for (Node n : LPCommunity.labelCommunitys.get(lab)) {
				nodeCommunitiy.put(n.getId(), cid);
			}
		}
	}

	/**
	 * caluculate modularity of a depth
	 * 
	 * @return Q
	 */
	private static float calcQ() {

		// cn is communitys number
		int cn = LPCommunity.communityLabel.size();

		float Q = 0;
		for (int k = 1; k <= cn; k++) {
			float ak = 0;
			float ekk = 0;
			for (long i = 1; i <= n; i++) {
				if (nodeCommunitiy.containsKey(i)) {
					ak += degree.get(i) * include(i, k);
					for (long j = 1; j <= n; j++) {
						if (nodeCommunitiy.containsKey(j)) {
							int m = 0;
							if (M.getAsInt(i - 1, j - 1) >= 1) {
								m = 1;
							}
							ekk += m * include(i, k) * include(j, k);
						}
					}
				}
			}
			ekk /= (2 * e);
			ak /= (2 * e);
			float ak2 = (float) Math.pow(ak, 2);
			Q += ekk - ak2;
		}
		return Q;
	}

	/**
	 * jude whether node is belong to community or not
	 * 
	 * @param nid
	 *            node id
	 * @param cid
	 *            community id
	 * @return 1 or 0
	 */
	private static int include(Long nid, long cid) {

		if (nodeCommunitiy.containsKey(nid)) {
			if (nodeCommunitiy.get(nid).equals(cid))
				return 1;
			else
				return 0;
		}
		return 0;
	}

}
